package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * "Because that, when they knew God, they glorified him not as God, neither were thankful; 
 *  but became vain in their imaginations, and their foolish heart was darkened.
 *  Professing themselves to be wise, they became fools," (Romans 1:21-22)
 *
 * ==========
 * Simple Xml and template solution.
 * 
 * @author jukka.rahkonen@iki.fi
 */
public class Xml extends Jugile implements Serializable {

	private String name; 
	public String name() { return name; }
	private String doctype;
	
	private String value; 
	public String value() { return value; }
	public void set(String v) { value = v; }
	public void _add(char ch) { value += ""+ch;}
	
	public Xml(String name) {
		this.name = name;
	}
	

	private List<Xml> children = new ArrayList<Xml>();
	public Xml add(String name) { Xml n = new Xml(name); _add(n); return n; }
	private void _add(Xml n) { children.add(n); n.parent = this; }
	public List<Xml> list() { return children; }
	public List<Xml> list(String name) {
		List<Xml> res = new ArrayList<Xml>();
		for (Xml n : children) if (eq(name,n.name)) res.add(n);
		return res;
	}
	public Xml get(String name) {
		for (Xml n : children) if (eq(name,n.name)) return n;
		return null;
	}
	public Xml getById(String id) {
		if (eq(attr("id"),id)) return this;
		for (Xml n : children) { Xml r = n.getById(id); if (r != null) return r; }
		return null;
	}
	public List<Xml> find(String path) {
		List<Xml> res = new ArrayList<Xml>();
		String p[] = path.split("\\.");
		if (p[0].startsWith("#")) {
			getById(p[0].substring(1))._find(p,1,res);
		} else _find(p,0,res);
		return res;
	}
	private void _find(String[] p, int idx, List<Xml> res) {
		String key = p[idx];
		//print("find: " +key);
		for (Xml n : children) {
			if (eq(key,n.name)) {
				//print("  found: " + key);
				if (idx == p.length-1) res.add(n);
				else n._find(p, idx+1, res);
			}
		}
	}
	
	private Xml parent;
	public Xml getParent() { return parent; }

	
	public String toString() {
		Buffer buf = new Buffer();
		dump(buf,null);
		return buf.toString();
	}
	
	public String eval(Object o) {
		Buffer buf = new Buffer();
		dump(buf,o);
		return buf.toString();
	}
	
	public void dump(Buffer buf, Object o) {
		if (o != null && !empty(attr("if"))) if (!evalIf(o)) return;
		if (doctype != null) buf.ln("<"+doctype+">");
		Iterable flst = new ArrayList();
		((List)flst).add(o);
		if (o != null && !empty(attr("for"))) flst = evalFor(o);
		for (Object i : flst) {
			buf.ind().add("<"+name);
			for (Attr a : attrs.values()) buf.add(a.eval(i));
			String val = getValue(i);
			if (empty(val) && children.size() == 0) {
				buf.add("/>");
			} else {
				buf.add(">");
				if (!empty(val)) {
					buf.add(val);
				} else {
					buf.nl();
					for (Xml n : children) {
						buf.incrIndent();
						n.dump(buf,i);
						buf.decrIndent();
					}
					buf.ind();
				}
				buf.add("</"+name+">");
			}
			buf.nl();
		}
	}
	
	private String getValue(Object o) {
		String txt = attr("txt");
		if (!empty(txt)) return getValue(txt,o);
		return evalStr(value, o);
	}
	
	private String getValue(String txt, Object o) {
		if (o == null) return "null";
		if (eq("this",txt)) return o.toString();
		Proxy p = new Proxy(o);
		Object r = p.get(txt);
		if (r == null) return "null";
		if (r instanceof String) return (String)r;
		return r.toString();		
	}
	
	private String evalStr(String str, Object o) {
		if (str == null) return null;
		if (str.length() == 0) return "";
		Buffer buf = new Buffer();
		PullReader pr = new PullReader(str);
		while (pr.hasNext()) {
			char ch = pr.next();
			if (ch == '@') {
				if (pr.next() == '(') {
					String var = pr.readUntil(")");
					print("var: " + var);
					pr.next();
					buf.add(getValue(var,o));
					continue;
				} else {
					pr.push(ch);
				}
			}
			buf.add(ch);
		}
		return buf.toString();
	}
	
	private boolean evalIf(Object o) {
		String ifexpr = attr("if");
		boolean not = false;
		if (ifexpr.startsWith("!")) {
			not = true;
			ifexpr = ifexpr.substring(1);
		}
		Proxy p = new Proxy(o);
		Object r = p.get(ifexpr);
		boolean res = false;
		if (r == null) res = false;   					  // null == false
		else if (r instanceof Boolean) res = (Boolean)r;  // boolean type
		else if (r instanceof Integer) res = ((Integer)r == 0); // 0 == false
		if (not) return !res;
		return res;
	}
	
	private Iterable evalFor(Object o) {
		String forexpr = attr("for");
		Proxy p = new Proxy(o);
		Object r = p.get(forexpr);
		if (r instanceof Iterable) return (Iterable)r;
		fail("not iterable: " + forexpr); return null;
	}
	
	public static Xml parse(File f) {
		PullReader pr = new PullReader(f);
		//Xml n = parseNode(pr,null);
		return doparse(pr);
	}
	
	private static Xml doparse(PullReader pr) {
		Buffer val = new Buffer();
		Xml node = null;
		String doctype = null;
		while (pr.hasNext()) {
			char ch = pr.next();
			if (ch == '<') {
				if (pr.getLine() == 1) {
					if (pr.peek() == '!') {
						doctype = pr.readUntilStr(">");
						print("doctype: " + doctype);
						continue;
					}
				}
				if (pr.match("!--")) {
					String comment = pr.readUntilStr("-->");
					print("comment: " + comment);
					continue;
				}
				String elem = pr.readUntilStr(">");
				if (isHtmlIgnore(elem)) val.add("<"+elem+">");
				else {
					node = handleElem(elem, val, node);
					if (doctype != null) {
						node.doctype = doctype;
						doctype = null;
					}
					val = new Buffer();
				}
				continue;
			}
			val.add(ch);
		}
		return node;
	}

	private static boolean isHtmlIgnore(String elem) {
		if (elem.startsWith("/")) elem = elem.substring(1);
		if (elem.endsWith("/")) elem = elem.substring(0,elem.length()-2);
		if (eq(elem,"b")) return true;
		if (eq(elem,"u")) return true;
		if (eq(elem,"i")) return true;
		if (eq(elem,"br")) return true;
		if (eq(elem,"hr")) return true;
		// TODO: list all html format tags
		return false;
	}
	
	private static Xml handleElem(String elem, Buffer val, Xml node) {
		print("elem: " + elem);
		elem = elem.trim();
		if (elem.startsWith("/")) {  // end tag
			node.value = val.toString();
			if (node.parent == null) return node;
			return node.parent;
		}
		PullReader pr = new PullReader(elem);
		String name = pr.readIdentifier();
		Xml n = new Xml(name);
		if (node != null) node._add(n);
		while (parseAttr(pr,n));
		if (elem.endsWith("/")) { // empty element
			return node;
		}
		return n;		
	}
		
	private static boolean parseAttr(PullReader pr, Xml n) {
		pr.trim();
		String name = pr.readIdentifier();
		if (!empty(name)) {
			pr.trim();
			if (pr.peek() == '=') {
				pr.next(); pr.trim();
				if (pr.next() == '"') {
					String val = pr.readUntil("\"");
					pr.next();
					n.attr(name, val);
				} else fail("parse error: " + pr.getPositionString());
			} else n.attr(name,null);
			return true;
		}
		return false;
	}
	
	private Map<String,Attr> attrs = new HashMap<String,Attr>();
	public String attr(String name) {
		Attr a = attrs.get(name);
		if (a == null) return null;
		return a.value;
	}
	public Xml attr(String name, String value) {
		Attr a = new Attr(name,value);
		attrs.put(name,a);
		return this;
	}
	
	public static class Attr {
		private String name;
		public String name() { return name; }
		private String value;
		public String value() { return value; }

		public Attr(String n, String v) { 
			name = n;
			value = v;
		}
		
		public String toString() {
			if (value == null) return name;
			return name+"=\"" + value + "\"";
		}
		
		public String eval(Object o) {
			if (o == null) return " " + toString();
			if (eq(name,"if")) return "";
			if (eq(name,"for")) return "";
			if (eq(name,"txt")) return "";
			if (eq(name,"set")) return "";
			return " " + toString();
		}
	}
	
	
	
	
}
